{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练集大小: 13209\n",
      "验证集大小: 3303\n",
      "测试集大小: 4128\n"
     ]
    }
   ],
   "source": [
    "import numpy as np  # 导入NumPy库，用于数值计算\n",
    "import pandas as pd  # 导入Pandas库，用于数据处理和分析\n",
    "import matplotlib.pyplot as plt  # 导入Matplotlib库的pyplot模块，用于绘图\n",
    "from sklearn.datasets import fetch_california_housing  # 从sklearn导入加州房价数据集\n",
    "from sklearn.model_selection import train_test_split  # 导入数据集分割工具\n",
    "from sklearn.preprocessing import StandardScaler  # 导入标准化处理工具\n",
    "import torch  # 导入PyTorch库，用于深度学习\n",
    "import torch.nn as nn  # 导入PyTorch的神经网络模块\n",
    "import torch.optim as optim  # 导入PyTorch的优化器模块\n",
    "from torch.utils.data import DataLoader, Dataset  # 导入数据加载和数据集类\n",
    "import os  # 导入操作系统接口模块\n",
    "\n",
    "# 设置中文字体支持\n",
    "plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签\n",
    "plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号\n",
    "\n",
    "# 加载加利福尼亚房价数据集\n",
    "housing = fetch_california_housing()  # 获取加州房价数据集\n",
    "X = housing.data  # 提取特征数据\n",
    "y = housing.target  # 提取目标变量（房价）\n",
    "\n",
    "# 数据拆分：训练集(60%)、验证集(20%)、测试集(20%)\n",
    "X_train_val, X_test, y_train_val, y_test = train_test_split(X, y, test_size=0.2, random_state=42)  # 首先分出测试集\n",
    "X_train, X_val, y_train, y_val = train_test_split(X_train_val, y_train_val, test_size=0.2, random_state=42)  # 再将剩余数据分为训练集和验证集\n",
    "\n",
    "print(f\"训练集大小: {X_train.shape[0]}\")  # 打印训练集样本数量\n",
    "print(f\"验证集大小: {X_val.shape[0]}\")  # 打印验证集样本数量\n",
    "print(f\"测试集大小: {X_test.shape[0]}\")  # 打印测试集样本数量\n",
    "\n",
    "# 数据标准化\n",
    "scaler = StandardScaler()  # 创建标准化处理器实例\n",
    "X_train_scaled = scaler.fit_transform(X_train)  # 对训练集进行拟合和转换\n",
    "X_val_scaled = scaler.transform(X_val)  # 使用相同的缩放参数转换验证集\n",
    "X_test_scaled = scaler.transform(X_test)  # 使用相同的缩放参数转换测试集\n",
    "\n",
    "# 自定义数据集类\n",
    "class HousingDataset(Dataset):  # 定义继承自Dataset的房价数据集类\n",
    "    def __init__(self, features, targets):  # 初始化方法，接收特征和目标变量\n",
    "        self.features = torch.FloatTensor(features)  # 将特征转换为PyTorch浮点张量\n",
    "        self.targets = torch.FloatTensor(targets).view(-1, 1)  # 将目标变量转换为列向量形式的张量\n",
    "        \n",
    "    def __len__(self):  # 定义获取数据集长度的方法\n",
    "        return len(self.features)  # 返回样本数量\n",
    "    \n",
    "    def __getitem__(self, idx):  # 定义获取单个样本的方法\n",
    "        return self.features[idx], self.targets[idx]  # 返回指定索引的特征和目标\n",
    "\n",
    "# 创建数据集实例\n",
    "train_dataset = HousingDataset(X_train_scaled, y_train)  # 创建训练数据集实例\n",
    "val_dataset = HousingDataset(X_val_scaled, y_val)  # 创建验证数据集实例\n",
    "test_dataset = HousingDataset(X_test_scaled, y_test)  # 创建测试数据集实例\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 加载数据，构建模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "RegressionModel(\n",
      "  (layer1): Linear(in_features=8, out_features=30, bias=True)\n",
      "  (activation): ReLU()\n",
      "  (output): Linear(in_features=30, out_features=1, bias=True)\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "# 创建数据加载器\n",
    "train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)  # 创建训练数据加载器，批次大小为32，随机打乱数据\n",
    "val_loader = DataLoader(val_dataset, batch_size=32)  # 创建验证数据加载器，批次大小为32\n",
    "test_loader = DataLoader(test_dataset, batch_size=32)  # 创建测试数据加载器，批次大小为32\n",
    "\n",
    "# 定义神经网络模型\n",
    "class RegressionModel(nn.Module):  # 定义回归模型类，继承自PyTorch的nn.Module\n",
    "    def __init__(self, input_dim):  # 初始化方法，接收输入维度参数\n",
    "        super(RegressionModel, self).__init__()  # 调用父类的初始化方法\n",
    "        self.layer1 = nn.Linear(input_dim, 30)  # 定义第一个全连接层，从输入维度到30个神经元\n",
    "        self.activation = nn.ReLU()  # 定义ReLU激活函数\n",
    "        self.output = nn.Linear(30, 1)  # 定义输出层，从30个神经元到1个输出\n",
    "        \n",
    "    def forward(self, x):  # 定义前向传播方法\n",
    "        x = self.activation(self.layer1(x))  # 输入通过第一层后应用ReLU激活函数\n",
    "        x = self.output(x)  # 将激活后的结果传入输出层得到最终预测\n",
    "        return x  # 返回模型预测结果\n",
    "\n",
    "# 初始化模型、损失函数和优化器\n",
    "input_dim = X_train.shape[1]  # 获取输入特征的维度\n",
    "model = RegressionModel(input_dim)  # 实例化回归模型\n",
    "criterion = nn.MSELoss()  # 使用均方误差作为损失函数\n",
    "optimizer = optim.Adam(model.parameters())  # 使用Adam优化器优化模型参数\n",
    "\n",
    "# 打印模型结构\n",
    "print(model)  # 输出模型的结构信息\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "训练进度:  10%|█         | 10/100 [00:06<00:47,  1.88it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "轮次 10/100, 训练损失: 0.3692, 验证损失: 0.3888, 全局步数: 4130\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "训练进度:  20%|██        | 20/100 [00:11<00:42,  1.86it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "轮次 20/100, 训练损失: 0.3321, 验证损失: 0.3641, 全局步数: 8260\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "训练进度:  30%|███       | 30/100 [00:16<00:41,  1.70it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "轮次 30/100, 训练损失: 0.3212, 验证损失: 0.3675, 全局步数: 12390\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "训练进度:  40%|████      | 40/100 [00:21<00:29,  2.05it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "轮次 40/100, 训练损失: 0.3170, 验证损失: 0.3536, 全局步数: 16520\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "训练进度:  50%|█████     | 50/100 [00:26<00:25,  2.00it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "轮次 50/100, 训练损失: 0.3036, 验证损失: 0.3513, 全局步数: 20650\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "训练进度:  60%|██████    | 60/100 [00:31<00:19,  2.06it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "轮次 60/100, 训练损失: 0.2999, 验证损失: 0.3588, 全局步数: 24780\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "训练进度:  70%|███████   | 70/100 [00:36<00:14,  2.09it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "轮次 70/100, 训练损失: 0.2976, 验证损失: 0.3424, 全局步数: 28910\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "训练进度:  80%|████████  | 80/100 [00:41<00:09,  2.14it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "轮次 80/100, 训练损失: 0.2970, 验证损失: 0.3422, 全局步数: 33040\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "训练进度:  90%|█████████ | 90/100 [00:45<00:05,  1.96it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "轮次 90/100, 训练损失: 0.2963, 验证损失: 0.3846, 全局步数: 37170\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "训练进度: 100%|██████████| 100/100 [00:50<00:00,  1.97it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "轮次 100/100, 训练损失: 0.2968, 验证损失: 0.3391, 全局步数: 41300\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "# 训练模型\n",
    "from tqdm import tqdm  # 导入进度条库tqdm用于显示训练进度\n",
    "epochs = 100  # 设置训练轮次为100\n",
    "train_losses = []  # 初始化存储训练损失的列表\n",
    "val_losses = []  # 初始化存储验证损失的列表\n",
    "\n",
    "global_step = 0  # 初始化全局步数计数器\n",
    "\n",
    "# 使用tqdm显示总体进度\n",
    "for epoch in tqdm(range(epochs), desc=\"训练进度\"):  # 遍历每个训练轮次，并使用tqdm显示进度\n",
    "    # 训练模式\n",
    "    model.train()  # 将模型设置为训练模式，启用梯度计算和批归一化等\n",
    "    running_loss = 0.0  # 初始化当前轮次的累计损失\n",
    "    running_mae = 0.0  # 初始化当前轮次的累计平均绝对误差\n",
    "    \n",
    "    for inputs, targets in train_loader:  # 遍历训练数据加载器中的每个批次\n",
    "        optimizer.zero_grad()  # 清除之前的梯度\n",
    "        outputs = model(inputs)  # 前向传播，获取模型预测结果\n",
    "        loss = criterion(outputs, targets)  # 计算预测结果与真实值之间的损失\n",
    "        loss.backward()  # 反向传播，计算梯度\n",
    "        optimizer.step()  # 更新模型参数\n",
    "        \n",
    "        running_loss += loss.item() * inputs.size(0)  # 累加批次损失到总损失中\n",
    "        \n",
    "        # 更新全局步数\n",
    "        global_step += 1  # 每处理一个批次，全局步数加1\n",
    "    \n",
    "    epoch_train_loss = running_loss / len(train_loader.dataset)  # 计算当前轮次的平均训练损失\n",
    "\n",
    "    train_losses.append(epoch_train_loss)  # 将当前轮次的训练损失添加到训练损失列表中\n",
    "    \n",
    "    # 验证模式\n",
    "    model.eval()  # 将模型设置为评估模式，禁用梯度计算和批归一化等\n",
    "    running_loss = 0.0  # 重置累计损失用于验证集\n",
    "    \n",
    "    with torch.no_grad():  # 在评估模式下不计算梯度，节省内存\n",
    "        for inputs, targets in val_loader:  # 遍历验证数据加载器中的每个批次\n",
    "            outputs = model(inputs)  # 前向传播，获取模型预测结果\n",
    "            loss = criterion(outputs, targets)  # 计算预测结果与真实值之间的损失\n",
    "            \n",
    "            running_loss += loss.item() * inputs.size(0)  # 累加批次损失到总损失中\n",
    "\n",
    "    epoch_val_loss = running_loss / len(val_loader.dataset)  # 计算当前轮次的平均验证损失\n",
    "\n",
    "    val_losses.append(epoch_val_loss)  # 将当前轮次的验证损失添加到验证损失列表中\n",
    "    \n",
    "    if (epoch + 1) % 10 == 0:  # 每10个轮次打印一次训练信息\n",
    "        print(f'轮次 {epoch+1}/{epochs}, 训练损失: {epoch_train_loss:.4f}, 验证损失: {epoch_val_loss:.4f}, 全局步数: {global_step}')  # 打印当前训练进度和损失信息\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练集 - 均方误差: 0.2898\n",
      "验证集 - 均方误差: 0.3391\n",
      "测试集 - 均方误差: 0.3308\n"
     ]
    }
   ],
   "source": [
    "# 评估模型\n",
    "def evaluate(model, dataloader, criterion):\n",
    "    model.eval()\n",
    "    running_loss = 0.0\n",
    "\n",
    "    \n",
    "    with torch.no_grad():\n",
    "        for inputs, targets in dataloader:\n",
    "            outputs = model(inputs) #前向计算\n",
    "            loss = criterion(outputs, targets) #计算损失\n",
    "            \n",
    "            running_loss += loss.item() * inputs.size(0)\n",
    "    \n",
    "    return running_loss / len(dataloader.dataset)\n",
    "\n",
    "train_loss = evaluate(model, train_loader, criterion)\n",
    "val_loss= evaluate(model, val_loader, criterion)\n",
    "test_loss = evaluate(model, test_loader, criterion)\n",
    "\n",
    "print(f\"训练集 - 均方误差: {train_loss:.4f}\")\n",
    "print(f\"验证集 - 均方误差: {val_loss:.4f}\")\n",
    "print(f\"测试集 - 均方误差: {test_loss:.4f}\")\n",
    "\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
